package algo3c3g3.model;

import java.util.*;

import org.jdom.Element;

import algo3c3g3.model.persistence.*;

/**
 * Representa un ambiene del juego. Contiene un mapa, las unidades,
 * posicion de entdada y salida del mapa, cantidad de uniades activas, perdidas
 * y generadas.
 */
public class World extends Observable implements Persistable
{
    private WorldMap map;
    
    private List<Unit> units = new ArrayList<Unit>();
    
    private Coord entrancePosition,
                  exitPosition;
    
    private int unitCount;
    
    private int savedUnits, killedUnits;
    
    /**
     * @param entrance Posicion de entrada de las unidades.
     * @param exit Posicion de salida de las unidades.
     * @param map Mapa de este mundo.
     */
    public World(Coord entrance, Coord exit, WorldMap map)
    {
        this.map = map;
        entrancePosition = entrance;
        exitPosition = exit;
    }
    
    public World(Element e)
    {
        this.load(e);		
    }

    /**
     * Agrega una nueva unidad al mundo.
     * La posicion inicial la unidad es igual la posicion de entrada del mundo.
     * Asigna un numero secuencial como ID a cada unidad nueva.
     */
    public synchronized void addUnit()
    {
        units.add(new Unit(unitCount, entrancePosition, map, this));
        unitCount++;
        
        setChanged();
        notifyObservers();
    }
    
    /**
     * Devuelve una referencia a la unidad en el mundo correspondiente
     * al ID especificado.
     * @param id ID de la unidad a buscar.
     */
    public synchronized Unit getUnit(int id)
    {
        Iterator<Unit> i = units.iterator();
        
        Unit u = null;
        
        while(i.hasNext())
        {
            u = i.next();
            
            if(u.getID() == id)
                return u;
        }
        
        return null;
    }
    
    /**
     * Devuelve una referencia a la unidad en el mundo ubicada en
     * la coordenada especificada.
     * @param position Coordenadas de la unidad a buscar.
     */
    public synchronized Unit getUnit(Coord position)
    {
        return getUnit(position.getXCoord(), position.getYCoord());
    }
    
    /**
     * Devuelve una referencia a la unidad en el mundo ubicada en
     * la coordenada especificada.
     * @param x, y Coordenadas de la unidad a buscar.
     */
    public synchronized Unit getUnit(int x, int y)
    {
        Iterator<Unit> i = units.iterator();
        
        Unit u = null;
        
        while(i.hasNext())
        {
            u = i.next();
            
            if((u.getXPosition() == x) && (u.getYPosition() == y))
                return u;
        }
        
        return null;
    }
    
    public List<Unit> getAllUnits()
    {
        return units;
    }
    
    /**
     * Devuelve la cantidad de unidades que fueron creadas en este nivel
     * esten activas o no.
     */
    public int getUnitQty()
    {
        return unitCount;
    }
    
    public int savedUnits()
    {
        return savedUnits;
    }
    
    public synchronized int killedUnits()
    {
        return killedUnits;
    }
    
    public synchronized void notifyKilledUnit()
    {
        killedUnits++;
        
        setChanged();
        notifyObservers();
    }
    
    public synchronized int activeUnits()
    {
        return unitCount - savedUnits - killedUnits;
    }
    
    
    /**
     * Ejecuta la accion de todas las unidades en el mundo.
     */
    public synchronized void moveUnits()
    {
        Iterator<Unit> i = units.iterator();
        
        while(i.hasNext())
        {
            Unit u = i.next();
            u.performAction();
            checkSaved(u);
        }
        
        setChanged();
        notifyObservers();
    }
    
    private boolean checkSaved(Unit u)
    {
        if((u.getXPosition() == exitPosition.getXCoord()) &&
          (u.getYPosition() == exitPosition.getYCoord()) &&
          (u.isAlive()))
        {
            savedUnits++;
            u.setSaved();
            
            setChanged();
            notifyObservers();
        
            return true;
        }
        
        return false;
    }
    
    public WorldMap getMap()
    {
        return map;
    }
    
    public void printMap()
    {
        map.printMap();
    }
    
    public Coord getEntrancePosition()
    {
        return entrancePosition;
    }
    
    public Coord getExitPosition()
    {
        return exitPosition;
    }
    
    public void load(Element e)
    {
        this.map = new WorldMap(e.getChild("WorldMap"));

        Iterator<Element> i = e.getChild("units").getChildren().iterator();

        while (i.hasNext())
        {
            this.addUnit(new Unit(i.next()));
        }
        this.entrancePosition = new Coord(e.getChild("entrancePosition"));
        this.exitPosition = new Coord(e.getChild("exitPosition"));
        this.unitCount = new Integer(e.getChildText("unitCount")).intValue();
    }

    private void addUnit(Unit unit)
    {
        unit.setWorldMap(this.map);
        unit.setWorld(this);
        this.units.add(unit);
    }

    public Element save()
    {
        Element root = new Element(this.getClass().getCanonicalName());

        root.addContent(this.map.save());

        Element e = new Element("units");

        Iterator<Unit> i = this.units.iterator();

        while (i.hasNext())
        {
            Unit u = i.next();
            e.addContent(u.save());
        }

        root.addContent(e);

        e = new Element("entrancePosition");
        e.addContent(this.entrancePosition.save());
        root.addContent(e);

        e = new Element("exitPosition");
        e.addContent(this.exitPosition.save());
        root.addContent(e);

        e = new Element("unitCount");
        e.addContent(Integer.toString(this.unitCount));
        root.addContent(e);

        return root;
    }
    
    
    
    public static void main(String[] args)
    {
        World world = new World(new Coord(2, 5), new Coord(0, 0), new TestMaps.TestWalkerMap());
        world.printMap();
        
        world.addUnit();
        world.addUnit();
        world.addUnit();
        
        Unit u = world.getUnit(0);
        u.performAction();
        u.printStatus();
        
        u = world.getUnit(1);
        u.printStatus();
        
        u = world.getUnit(2);
        u.printStatus();
        
        u = world.getUnit(new Coord(3, 5));
        u.printStatus();
        
        u = world.getUnit(2, 5);
        u.printStatus();
        
        try
        {
            world.getUnit(9).printStatus();
        }
        catch(NullPointerException e)
        {
            System.out.println("La unidad " + 9 + " no existe!!\n");
        }
        
        Persistence.toXMLFile(world.save(),"C:\\salida.xml");
        Persistence.toXMLFile(new World(
        		Persistence.getRootFromXMLFile("C:\\salida.xml")).save(),
        		"C:\\salida2.xml");
    }
}
